#include "wifi_x.h"

static const char *TAG = "m_wifi";

uint8_t mac[6]={0};

struct sockaddr_in dest_addr;
int sock = -99;
// char host_ip[] = HOST_IP_ADDR;

int addr_family = 0;
int ip_protocol = 0;

extern char gl_ip_str[];
extern char gl_port_str[];
extern char gl_ssid_str[];
extern char gl_pw_str[];

static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
 * - we are connected to the AP with an IP
 * - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1

static int s_retry_num = 0;

#define EXAMPLE_ESP_MAXIMUM_RETRY_X 32

struct timeval timeout;

struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
socklen_t socklen = sizeof(source_addr);

/**
 * @brief how to use: get_ip_from_sockaddr((struct sockaddr *)&dest_addr, ip_str, sizeof(ip_str));
 */
void get_ip_from_sockaddr(const struct sockaddr *sa, char *ip_addr, size_t ip_addr_len)
{
    if (sa->sa_family == AF_INET) // IPv4
    {
        struct sockaddr_in *sin = (struct sockaddr_in *)sa;
        inet_ntop(AF_INET, &sin->sin_addr, ip_addr, ip_addr_len);
    }
}

static void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)
{
    // ESP_LOGI(TAG, ": - - -: %s - %ld", event_base, event_id);

    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        esp_wifi_connect();
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY_X)
        {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP time %d", s_retry_num);
        }
        else
        {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG, "connect to the AP fail");
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            // .ssid = EXAMPLE_ESP_WIFI_SSID_X,
            // .password = EXAMPLE_ESP_WIFI_PASS_X,

            /* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8).
             * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
             * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
             * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
             */
            // .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
            // .threshold.authmode = WIFI_AUTH,
            .sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
        },
    };
    // strcpy(wifi_config.sta.ssid, (uint8_t*)gl_ssid_str);
    // strcpy(wifi_config.sta.password, (uint8_t*)gl_pw_str);
    strncpy((char *)wifi_config.sta.ssid, (char *)&gl_ssid_str[0], 32);
    strncpy((char *)wifi_config.sta.password, (char *)&gl_pw_str[0], 64);

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    // ESP_LOGI(TAG, "wifi_init_sta finished.");

    esp_err_t ret = esp_wifi_get_mac(ESP_IF_WIFI_STA, mac);
    if (ret != ESP_OK)
    {
        // printf("Error getting MAC address: %d\n", ret);
        return;
    }
    ESP_LOGI(TAG, "mac : %x, %x, %x, %x, %x, %x ", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
                                           WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                                           pdFALSE,
                                           pdFALSE,
                                           portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & WIFI_CONNECTED_BIT)
    {
        ESP_LOGI(TAG, "connected to ap SSID");
    }
    else if (bits & WIFI_FAIL_BIT)
    {
        ESP_LOGI(TAG, "Failed to connect ");
    }
    else
    {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }
}

int wifi_send_x(char *p_char, size_t char_len){
    return sendto(sock, p_char, char_len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
}

        // if (err < 0){
        //     ESP_LOGI(TAG, "send error");
        // }

void wifi_recv_x(char *p_char, size_t char_len)
{
        int len = recvfrom(sock, p_char, char_len, 0, (struct sockaddr *)&source_addr, &socklen);

        // Error occurred during receiving
        if (len < 0)
        {
            // ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
            // break;
        }
        // Data received
        else
        {
            p_char[char_len] = 0; // Null-terminate whatever we received and treat like a string
            ESP_LOGI(TAG, "Received %d bytes from %s:", len, gl_ip_str);
            ESP_LOGI(TAG, "%s", p_char);
            // if (strncmp(p_char, "OK: ", 4) == 0)
            // {
            //     ESP_LOGI(TAG, "Received expected message, reconnecting");
            //     return;
            // }
        }

        // if (sock != -1)
        // {
        //     ESP_LOGE(TAG, "Shutting down socket and restarting...");
        //     shutdown(sock, 0);
        //     close(sock);

        //     sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
        //     if (sock < 0) {
        //         ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        //         esp_restart();
        //     }
        // }
}

// static void udp_client_task_rcv(void *pvParameters)
// {
//     char rx_buffer[128];
//     // Set timeout
//     struct timeval timeout;
//     timeout.tv_sec = 6;
//     timeout.tv_usec = 0;
//     setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);

//     struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
//     socklen_t socklen = sizeof(source_addr);

//     while (1)
//     {

//         int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);

//         // Error occurred during receiving
//         if (len < 0)
//         {
//             // ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
//             // break;
//         }
//         // Data received
//         else
//         {
//             rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
//             ESP_LOGI(TAG, "Received %d bytes from %s:", len, gl_ip_str);
//             ESP_LOGI(TAG, "%s", rx_buffer);
//             if (strncmp(rx_buffer, "OK: ", 4) == 0)
//             {
//                 ESP_LOGI(TAG, "Received expected message, reconnecting");
//                 break;
//             }
//         }

//         // if (sock != -1)
//         // {
//         //     ESP_LOGE(TAG, "Shutting down socket and restarting...");
//         //     shutdown(sock, 0);
//         //     close(sock);

//         //     sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
//         //     if (sock < 0) {
//         //         ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
//         //         // esp_restart();
//         //     }
//         // }
//     }

//     vTaskDelete(NULL);
// }

/**
 * @brief the mac would be got on wifi_init_sta(); so call this after that
 */
void get_mac_str(char* p){
    sprintf(p, "%x:%x:%x:%x:%x:%x", mac[0],mac[1], mac[2],mac[3],mac[4],mac[5]);
}

void wifi_x_init(void)
{

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    s_wifi_event_group = xEventGroupCreate();
    wifi_init_sta();

    unsigned short number = (unsigned short)strtoul((const char *)gl_port_str, NULL, 0);

    dest_addr.sin_addr.s_addr = inet_addr(gl_ip_str);
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(number);
    addr_family = AF_INET;
    ip_protocol = IPPROTO_IP;

    sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
    if (sock < 0)
    {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        esp_restart();
    }

    timeout.tv_sec = 6;
    timeout.tv_usec = 0;
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);
}
